package com.app.updater

import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.util.Log
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.security.MessageDigest


/** AppUpdaterPlugin */
class AppUpdaterPlugin : FlutterPlugin, MethodCallHandler {
    private lateinit var binding: FlutterPlugin.FlutterPluginBinding

    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private lateinit var channel: MethodChannel

    private val context: Context
        get() {
            return binding.applicationContext
        }
    private val downloadManager: DownloadManager
        get() {
            return context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
        }

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        binding = flutterPluginBinding

        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter/app_updater")
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        try {
            when (call.method) {
                "getPlatformVersion" -> {
                    result.success("Android ${Build.VERSION.RELEASE}")
                }
                "enqueueDownload" -> {
                    val url = call.argument<String>("url")!!
                    val localPath = call.argument<String>("localPath")
                    val headers = call.argument<Map<String, String>>("headers")
                    val title = call.argument<String>("title")
                    val description = call.argument<String>("description")
                    val id = enqueueDownload(url, localPath, headers, title, description)
                    return result.success(id)
                }
                "removeDownload" -> {
                    val ids = call.arguments<List<Long>>()!!
                    val numOfRemoved = removeDownload(*ids.toLongArray())
                    return result.success(numOfRemoved)
                }
                "getDownloadStatus" -> {
                    val id = call.arguments<Long>()!!
                    val status = getDownloadStatus(id)
                    return result.success(status)
                }
                "getUriForDownloadedFile" -> {
                    val id = call.arguments<Long>()!!
                    val uri = getUriForDownloadedFile(id)
                    return result.success(uri?.toString())
                }
                "calculateMD5Checksum" -> {
                    val id = call.arguments<Long>()!!
                    val checksum = calculateMD5Checksum(id)
                    return result.success(checksum)
                }
                "requestInstallAPK" -> {
                    val uriString = call.arguments<String>()!!
                    requestInstallAPK(uriString)
                    return result.success(null)
                }
                else ->
                    result.notImplemented()
            }
        } catch (error: Exception) {
            result.error("-1", error.message, null)
        }
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }

    private fun enqueueDownload(
        url: String,
        localPath: String?,
        headers: Map<String, String>?,
        title: String?,
        description: String?,
    ): Long {
        val uri = Uri.parse(url)
        val request = DownloadManager.Request(uri).also {
            it.allowScanningByMediaScanner()
        }
        if (localPath != null) {
            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, localPath)
        }
        headers?.forEach {
            request.addRequestHeader(it.key, it.value)
        }
        if (title != null) {
            request.setTitle(title)
        }
        if (description != null) {
            request.setDescription(description)
        }

        return downloadManager.enqueue(request)
    }

    private fun removeDownload(vararg ids: Long): Int {
        return downloadManager.remove(*ids)
    }

    private fun getDownloadStatus(downloadId: Long): Map<String, Any>? {
        var result: Map<String, Any>? = null

        val query = DownloadManager.Query().setFilterById(downloadId)
        downloadManager.query(query)?.use {
            if (it.moveToFirst()) {
                val i0 = it.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)
                val i1 = it.getColumnIndexOrThrow(DownloadManager.COLUMN_TITLE)
                val i2 = it.getColumnIndexOrThrow(DownloadManager.COLUMN_DESCRIPTION)
                val i3 = it.getColumnIndexOrThrow(DownloadManager.COLUMN_URI)
                val i4 = it.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
                val i5 = it.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI)
                val i6 = it.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)
                val i7 = it.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)

                result = mutableMapOf(
                    Pair("id", it.getInt(i0)),
                    Pair("title", it.getString(i1)),
                    Pair("description", it.getString(i2)),
                    Pair("uri", it.getString(i3)),
                    Pair("totalSizeBytes", it.getInt(i4)),
                    Pair("localUri", it.getString(i5)),
                    Pair("status", it.getInt(i6)),
                    Pair("bytesDownloadedSoFar", it.getInt(i7)),
                )
            }
        }

        return result
    }

    private fun getUriForDownloadedFile(id: Long): Uri? {
        return downloadManager.getUriForDownloadedFile(id)
    }

    private fun calculateMD5Checksum(id: Long): ByteArray {
        val fileDescriptor = downloadManager.openDownloadedFile(id) ?: throw FileNotFoundException()

        val digest = MessageDigest.getInstance("MD5")
        FileInputStream(fileDescriptor.fileDescriptor).use {
            val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
            var bytes: Int
            do {
                bytes = it.read(buffer)
                if (bytes < 0) {
                    break
                } else {
                    digest.update(buffer, 0, bytes)
                }
            } while (true)
        }

        return digest.digest()
    }

    private fun requestInstallAPK(uriString: String) {
        val uri = Uri.parse(uriString)
        val intent = Intent(Intent.ACTION_VIEW).also {
            it.setDataAndType(uri, "application/vnd.android.package-archive")
            it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                it.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            } else {
                it.addCategory(Intent.CATEGORY_DEFAULT)
            }
        }
        context.startActivity(intent)

        Log.d(this::class.simpleName, "launchAPKInstallation with uri: $uriString")
    }
}
